use std::f64;

use autograd::forward::Variable;

/// A very simple example using Newton-Raphson method and forward automatic derivative
/// to get a zero point of a function, ignoring no zero, sluggish and so on.
/// use x' = x - f(x)/f'(x) to iterate
fn newton_raphson(f: impl Fn(Variable) -> Variable, x_init: f64) -> f64 {
    let mut x = Variable::new_diff(x_init);
    while f(x).value().abs() > f64::EPSILON * 1e3 {
        let (value, grad) = f(x).value_grad();
        println!("x:{},\tvalue:{},\tgrad:{}", x.value(), value, grad);
        x = x - value / grad;
    }
    x.value()
}

fn main() {
    let sin = |x: Variable| x.sin();
    let zero_point = newton_raphson(sin, 4.0);
    println!("{}", zero_point); // ~PI

    let zero_point = newton_raphson(f1, 5.0);
    println!("{}", zero_point);

    let zero_point = newton_raphson(f2, 0.5);
    println!("{}", zero_point);

    let zero_point = newton_raphson(f2, 4.0);
    println!("{}", zero_point);

    let zero_point = newton_raphson(f2, -4.0);
    println!("{}", zero_point);
}

// f(x) = sqrt(x) - exp(PI/x)
fn f1(x: Variable) -> Variable {
    x.sqrt() - Variable::exp(x.recip() * f64::consts::PI)
}

// f(x) = arctan(x)*2/PI - tanh(x) * E/PI
fn f2(x: Variable) -> Variable {
    x.atan() * (f64::consts::FRAC_2_PI) - x.tanh() * (f64::consts::E / f64::consts::PI)
}
